const AESUtil = require('./AESUtil');
const TextEncoder = require('./Encoder');

/**
 * Command utility for NFC communication
 */
class CMD {
  /**
   * Command for get ID.
   * @return {Array<number>} Return the command list.
   */
  static ID() {
    return [
      0x00080001,
      Math.floor(Math.random() * 0xFFFFFFFF),
      0x09080005
    ];
  }

  /**
   * Command for get user count.
   * @return {Array<number>} Return the command list.
   */
  static CNT() {
    return [
      0x00080001,
      Math.floor(Math.random() * 0xFFFFFFFF),
      0x03010030
    ];
  }

  /**
   * Command for select user.
   * @param {number} type User type (0x01 = user, 0xFE = admin).
   * @return {Array<number>} Return the command list.
   */
  static SEL(type) {
    return [
      0x000C0001,
      Math.floor(Math.random() * 0xFFFFFFFF),
      0x83011900,
      ((type << 24) & (0xFF << 24)) + (Math.floor(Math.random() * 0xFFFFFFFF) & 0x00FFFFFF)
    ];
  }

  /**
   * Command for set datetime.
   * @param {Uint8Array} eKey Key for encryption.
   * @return {Array<number>} Return the command list.
   */
  static DT(eKey) {
    return AESUtil.encryptUInt(
      [
        0x00108001,
        Math.floor(Math.random() * 0xFFFFFFFF),
        0x88081800,
        0x00000000,
        Math.floor(Date.now() / 1000)
      ],
      1,
      4,
      eKey,
      new Uint8Array(16).fill(0)
    );
  }

  /**
   * Command for set username.
   * @param {Uint8Array} eKey Key for encryption.
   * @param {string} name Username.
   * @return {Array<number>} Return the command list.
   */
  static USR(eKey, name) {
    // Convert string to byte array
    const encoder = new TextEncoder();
    const nameBytes = encoder.encode(name);
    const nameLength = nameBytes.length;
    
    // Calculate padding
    const padding = 16 - (nameLength + 8) % 16;
    
    // Create padded array
    const paddedBytes = new Uint8Array(nameLength + padding);
    paddedBytes.set(nameBytes);
    
    // Fill padding with random bytes
    for (let i = nameLength; i < paddedBytes.length; i++) {
      paddedBytes[i] = Math.floor(Math.random() * 256);
    }
    
    // Convert bytes to UInts
    const nameUInts = [];
    for (let i = 0; i < paddedBytes.length; i += 4) {
      nameUInts.push(
        ((paddedBytes[i] || 0) << 24) |
        ((paddedBytes[i + 1] || 0) << 16) |
        ((paddedBytes[i + 2] || 0) << 8) |
        (paddedBytes[i + 3] || 0)
      );
    }
    
    // Calculate total data bytes
    const totalDataBytes = 4 + 4 + nameUInts.length * 4;
    const dataLenPrefix = ((totalDataBytes << 16) | 0x8001);
    const nameLenField = (0x91 << 24) | (nameLength << 16) | 0x1801;
    
    // Build command list
    const commandList = [
      dataLenPrefix,
      Math.floor(Math.random() * 0xFFFFFFFF),
      nameLenField,
      ...nameUInts
    ];
    
    return AESUtil.encryptUInt(
      commandList,
      1,
      commandList.length - 1,
      eKey,
      new Uint8Array(16).fill(0)
    );
  }

  /**
   * Command for set device key.
   * @param {Uint8Array} eKey Key for encryption.
   * @param {Uint8Array} key Device key.
   * @return {Array<number>} Return the command list.
   */
  static KEY(eKey, key) {
    if (key.length > 16) {
      throw new Error("Key size must be at most 16");
    }
    
    // Convert key bytes to UInts
    const keyUInts = [];
    for (let i = 0; i < key.length; i += 4) {
      keyUInts.push(
        ((key[i] || 0) << 24) |
        ((key[i + 1] || 0) << 16) |
        ((key[i + 2] || 0) << 8) |
        (key[i + 3] || 0)
      );
    }
    
    // Build command list
    const commandList = [
      0x00208001,
      Math.floor(Math.random() * 0xFFFFFFFF),
      0x90101902,
      ...keyUInts,
      Math.floor(Math.random() * 0xFFFFFFFF),
      Math.floor(Math.random() * 0xFFFFFFFF)
    ];
    
    return AESUtil.encryptUInt(
      commandList,
      1,
      commandList.length - 1,
      eKey,
      new Uint8Array(16).fill(0)
    );
  }

  /**
   * Command for check key.
   * @param {Uint8Array} eKey Key for encryption.
   * @return {Array<number>} Return the command list.
   */
  static CHK(eKey) {
    return AESUtil.encryptUInt(
      [
        0x00108001,
        Math.floor(Math.random() * 0xFFFFFFFF),
        0x10101903,
        Math.floor(Math.random() * 0xFFFFFFFF),
        Math.floor(Math.random() * 0xFFFFFFFF)
      ],
      1,
      4,
      eKey,
      new Uint8Array(16).fill(0)
    );
  }

  /**
   * Command for store key.
   * @param {Uint8Array} eKey Key for encryption.
   * @return {Array<number>} Return the command list.
   */
  static STO(eKey) {
    return AESUtil.encryptUInt(
      [
        0x00108001,
        Math.floor(Math.random() * 0xFFFFFFFF),
        0x87041901,
        0x82D112E6,
        Math.floor(Math.random() * 0xFFFFFFFF)
      ],
      1,
      4,
      eKey,
      new Uint8Array(16).fill(0)
    );
  }

  /**
   * Command for get charge raw threshold.
   * @param {Uint8Array} eKey Key for encryption.
   * @return {Array<number>} Return the command list.
   */
  static CGRT(eKey) {
    return AESUtil.encryptUInt(
      [
        0x00108001,
        Math.floor(Math.random() * 0xFFFFFFFF),
        0x05020011,
        Math.floor(Math.random() * 0xFFFFFFFF),
        Math.floor(Math.random() * 0xFFFFFFFF)
      ],
      1,
      4,
      eKey,
      new Uint8Array(16).fill(0)
    );
  }

  /**
   * Command for get charge raw.
   * @param {Uint8Array} eKey Key for encryption.
   * @return {Array<number>} Return the command list.
   */
  static CGR(eKey) {
    return AESUtil.encryptUInt(
      [
        0x00108001,
        Math.floor(Math.random() * 0xFFFFFFFF),
        0x05020010,
        Math.floor(Math.random() * 0xFFFFFFFF),
        Math.floor(Math.random() * 0xFFFFFFFF)
      ],
      1,
      4,
      eKey,
      new Uint8Array(16).fill(0)
    );
  }

  /**
   * Command for get device ready status.
   * @param {Uint8Array} eKey Key for encryption.
   * @param {number} ctrl Control type.
   * @return {Array<number>} Return the command list.
   */
  static ARM(eKey, ctrl) {
    return AESUtil.encryptUInt(
      [
        0x00108001,
        Math.floor(Math.random() * 0xFFFFFFFF),
        0x83010121,
        ((ctrl << 24) & (0xFF << 24)) + (Math.floor(Math.random() * 0xFFFFFFFF) & 0x00FFFFFF),
        Math.floor(Math.random() * 0xFFFFFFFF)
      ],
      1,
      4,
      eKey,
      new Uint8Array(16).fill(0)
    );
  }

  /**
   * Command for control device.
   * @param {Uint8Array} eKey Key for encryption.
   * @param {number} ctrl Control type.
   * @return {Array<number>} Return the command list.
   */
  static CTRL(eKey, ctrl) {
    return AESUtil.encryptUInt(
      [
        0x00108001,
        Math.floor(Math.random() * 0xFFFFFFFF),
        0x83010120,
        ((ctrl << 24) & (0xFF << 24)) + (Math.floor(Math.random() * 0xFFFFFFFF) & 0x00FFFFFF),
        Math.floor(Math.random() * 0xFFFFFFFF)
      ],
      1,
      4,
      eKey,
      new Uint8Array(16).fill(0)
    );
  }

  /**
   * Command for get device control progress.
   * @param {Uint8Array} eKey Key for encryption.
   * @return {Array<number>} Return the command list.
   */
  static CTRP(eKey) {
    return AESUtil.encryptUInt(
      [
        0x00108001,
        Math.floor(Math.random() * 0xFFFFFFFF),
        0x03010021,
        Math.floor(Math.random() * 0xFFFFFFFF),
        Math.floor(Math.random() * 0xFFFFFFFF)
      ],
      1,
      4,
      eKey,
      new Uint8Array(16).fill(0)
    );
  }

  /**
   * Command for set control method.
   * @param {Uint8Array} eKey Key for encryption.
   * @param {number} value Value of control method.
   * @return {Array<number>} Return the command list.
   */
  static MET(eKey, value) {
    return AESUtil.encryptUInt(
      [
        0x00108001,
        Math.floor(Math.random() * 0xFFFFFFFF),
        0x83011000,
        ((value << 24) & (0xFF << 24)) + (Math.floor(Math.random() * 0xFFFFFFFF) & 0x00FFFFFF),
        Math.floor(Math.random() * 0xFFFFFFFF)
      ],
      1,
      4,
      eKey,
      new Uint8Array(16).fill(0)
    );
  }

  /**
   * Command for set clamping voltage.
   * @param {Uint8Array} eKey Key for encryption.
   * @param {number} value Value of clamping voltage.
   * @return {Array<number>} Return the command list.
   */
  static CLPV(eKey, value) {
    return AESUtil.encryptUInt(
      [
        0x00108001,
        Math.floor(Math.random() * 0xFFFFFFFF),
        0x85021001,
        ((value << 16) & (0xFFFF << 16)) + (Math.floor(Math.random() * 0xFFFFFFFF) & 0x0000FFFF),
        Math.floor(Math.random() * 0xFFFFFFFF)
      ],
      1,
      4,
      eKey,
      new Uint8Array(16).fill(0)
    );
  }

  /**
   * Command for set total motor runtime.
   * @param {Uint8Array} eKey Key for encryption.
   * @param {number} value Value of total motor runtime.
   * @return {Array<number>} Return the command list.
   */
  static TTMT(eKey, value) {
    return AESUtil.encryptUInt(
      [
        0x00108001,
        Math.floor(Math.random() * 0xFFFFFFFF),
        0x85021007,
        ((value << 16) & (0xFFFF << 16)) + (Math.floor(Math.random() * 0xFFFFFFFF) & 0x0000FFFF),
        Math.floor(Math.random() * 0xFFFFFFFF)
      ],
      1,
      4,
      eKey,
      new Uint8Array(16).fill(0)
    );
  }

  /**
   * Command for set single movement start voltage.
   * @param {Uint8Array} eKey Key for encryption.
   * @param {number} value Value of single movement start voltage.
   * @return {Array<number>} Return the command list.
   */
  static SINSTV(eKey, value) {
    return AESUtil.encryptUInt(
      [
        0x00108001,
        Math.floor(Math.random() * 0xFFFFFFFF),
        0x85021006,
        ((value << 16) & (0xFFFF << 16)) + (Math.floor(Math.random() * 0xFFFFFFFF) & 0x0000FFFF),
        Math.floor(Math.random() * 0xFFFFFFFF)
      ],
      1,
      4,
      eKey,
      new Uint8Array(16).fill(0)
    );
  }

  /**
   * Command for set voltage controlled start voltage.
   * @param {Uint8Array} eKey Key for encryption.
   * @param {number} value Value of voltage controlled start voltage.
   * @return {Array<number>} Return the command list.
   */
  static VOLSTV(eKey, value) {
    return AESUtil.encryptUInt(
      [
        0x00108001,
        Math.floor(Math.random() * 0xFFFFFFFF),
        0x85021002,
        ((value << 16) & (0xFFFF << 16)) + (Math.floor(Math.random() * 0xFFFFFFFF) & 0x0000FFFF),
        Math.floor(Math.random() * 0xFFFFFFFF)
      ],
      1,
      4,
      eKey,
      new Uint8Array(16).fill(0)
    );
  }

  /**
   * Command for set voltage controlled stop voltage.
   * @param {Uint8Array} eKey Key for encryption.
   * @param {number} value Value of voltage controlled stop voltage.
   * @return {Array<number>} Return the command list.
   */
  static VOLSPV(eKey, value) {
    return AESUtil.encryptUInt(
      [
        0x00108001,
        Math.floor(Math.random() * 0xFFFFFFFF),
        0x85021003,
        ((value << 16) & (0xFFFF << 16)) + (Math.floor(Math.random() * 0xFFFFFFFF) & 0x0000FFFF),
        Math.floor(Math.random() * 0xFFFFFFFF)
      ],
      1,
      4,
      eKey,
      new Uint8Array(16).fill(0)
    );
  }

  /**
   * Command for set timer controlled on time.
   * @param {Uint8Array} eKey Key for encryption.
   * @param {number} value Value of timer controlled on time.
   * @return {Array<number>} Return the command list.
   */
  static TIMONT(eKey, value) {
    return AESUtil.encryptUInt(
      [
        0x00108001,
        Math.floor(Math.random() * 0xFFFFFFFF),
        0x85021004,
        ((value << 16) & (0xFFFF << 16)) + (Math.floor(Math.random() * 0xFFFFFFFF) & 0x0000FFFF),
        Math.floor(Math.random() * 0xFFFFFFFF)
      ],
      1,
      4,
      eKey,
      new Uint8Array(16).fill(0)
    );
  }

  /**
   * Command for set timer controlled off time.
   * @param {Uint8Array} eKey Key for encryption.
   * @param {number} value Value of timer controlled off time.
   * @return {Array<number>} Return the command list.
   */
  static TIMOFT(eKey, value) {
    return AESUtil.encryptUInt(
      [
        0x00108001,
        Math.floor(Math.random() * 0xFFFFFFFF),
        0x85021005,
        ((value << 16) & (0xFFFF << 16)) + (Math.floor(Math.random() * 0xFFFFFFFF) & 0x0000FFFF),
        Math.floor(Math.random() * 0xFFFFFFFF)
      ],
      1,
      4,
      eKey,
      new Uint8Array(16).fill(0)
    );
  }

  /**
   * Command for get log count.
   * @param {Uint8Array} eKey Key for encryption.
   * @return {Array<number>} Return the command list.
   */
  static LGC(eKey) {
    return AESUtil.encryptUInt(
      [
        0x00108001,
        Math.floor(Math.random() * 0xFFFFFFFF),
        0x05021810,
        Math.floor(Math.random() * 0xFFFFFFFF),
        Math.floor(Math.random() * 0xFFFFFFFF)
      ],
      1,
      4,
      eKey,
      new Uint8Array(16).fill(0)
    );
  }

  /**
   * Command for select log.
   * @param {Uint8Array} eKey Key for encryption.
   * @param {number} index Log index.
   * @return {Array<number>} Return the command list.
   */
  static LGS(eKey, index) {
    return AESUtil.encryptUInt(
      [
        0x00108001,
        Math.floor(Math.random() * 0xFFFFFFFF),
        0x85021811,
        ((index << 16) & (0xFFFF << 16)) + (Math.floor(Math.random() * 0xFFFFFFFF) & 0x0000FFFF),
        Math.floor(Math.random() * 0xFFFFFFFF)
      ],
      1,
      4,
      eKey,
      new Uint8Array(16).fill(0)
    );
  }

  /**
   * Command for get datetime of selected log.
   * @param {Uint8Array} eKey Key for encryption.
   * @return {Array<number>} Return the command list.
   */
  static LGD(eKey) {
    return AESUtil.encryptUInt(
      [
        0x00108001,
        Math.floor(Math.random() * 0xFFFFFFFF),
        0x08081813,
        Math.floor(Math.random() * 0xFFFFFFFF),
        Math.floor(Math.random() * 0xFFFFFFFF)
      ],
      1,
      4,
      eKey,
      new Uint8Array(16).fill(0)
    );
  }

  /**
   * Command for get username of selected log.
   * @param {Uint8Array} eKey Key for encryption.
   * @return {Array<number>} Return the command list.
   */
  static LGU(eKey) {
    return AESUtil.encryptUInt(
      [
        0x00108001,
        Math.floor(Math.random() * 0xFFFFFFFF),
        0x11281814,
        Math.floor(Math.random() * 0xFFFFFFFF),
        Math.floor(Math.random() * 0xFFFFFFFF)
      ],
      1,
      4,
      eKey,
      new Uint8Array(16).fill(0)
    );
  }

  /**
   * Command for get status of selected log.
   * @param {Uint8Array} eKey Key for encryption.
   * @return {Array<number>} Return the command list.
   */
  static LGT(eKey) {
    return AESUtil.encryptUInt(
      [
        0x00108001,
        Math.floor(Math.random() * 0xFFFFFFFF),
        0x07041812,
        Math.floor(Math.random() * 0xFFFFFFFF),
        Math.floor(Math.random() * 0xFFFFFFFF)
      ],
      1,
      4,
      eKey,
      new Uint8Array(16).fill(0)
    );
  }
}

module.exports = CMD;
